using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using ResourceLoader.DeserializeTool;
using ResourceLoader.Extensions;

namespace ResourceLoader.Loader
{
    /// <summary>
    /// 单个包信息
    /// </summary>
    public class SinglePackage : IDisposable
    {
        private string _basePath; //基础路径
        public Dictionary<string, ResourceInfo> ResourceDictionary { get; private set; } //资源索引字典
        private FileStream _fileStream; //资源文件流
        private BinaryReader _binaryReader; //资源文件二进制读取
        private long _dictionaryByteLength; //资源字典二进制长度
        private object _locker = new object(); //多线程锁

        public SinglePackage(string basePath)
        {
            _basePath = basePath;
            _fileStream = new FileStream(_basePath, FileMode.Open, FileAccess.Read);
            _binaryReader = new BinaryReader(_fileStream);
            LoadDictionary();
        }

        /// <summary>
        /// 读取并加载资源字典
        /// </summary>
        private void LoadDictionary()
        {
            //获得字典的字节流长度
            byte[] bytes = new byte[8];
            Load(bytes, 0, 8);
            _dictionaryByteLength = BitConverter.ToInt64(bytes, 0);

            //根据字节流长度,获得字典
            byte[] dicBytes = new byte[_dictionaryByteLength];
            Load(dicBytes, 8, _dictionaryByteLength);
            ResourceDictionary = DeserializableHeader.ToDictionary(dicBytes);
        }

        //提供给用户一个查询文件相关信息的api, 让用户自己可以掌控开辟buffer的大小
        /// <summary>
        /// 根据偏移位置,加载资源
        /// </summary>
        /// <param name="array"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public long Load([In, Out] byte[] array, long offset, long count)
        {
            lock (_locker)
            {
                long readCount = 0;
                _fileStream.Seek(offset, SeekOrigin.Begin);
                //此处添加了FileStream的一个扩展方法, 为了满足long型
                //为了兼顾小文件读取速度不受影响, 所以在此加以判断
                if (count <= int.MaxValue)
                {
                    readCount = _fileStream.Read(array, 0, (int) count);
                }
                else
                    readCount = _fileStream.Read(array, 0, count);

                return readCount; 
            }
        }

        /// <summary>
        /// 获得文件
        /// </summary>
        /// <param name="array"></param>
        /// <param name="resourceInfo"></param>
        public long Load([In, Out] byte[] array, ResourceInfo resourceInfo)
        {
            long trueOffset = resourceInfo.Index + 8 + _dictionaryByteLength;
            return Load(array, trueOffset, resourceInfo.Length);
        }

        /// <summary>
        /// 根据资源相对路径加载资源
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public long Load([In, Out] byte[] bytes, string path)
        {
            ResourceInfo resLocation = TryGetFileInfo(path);

            if (resLocation.IsNull())
            {
                throw new Exception($"未在{_basePath}中找到文件{path}");
            }

            return Load(bytes, resLocation.Index + 8 + _dictionaryByteLength, resLocation.Length);
        }

        /// <summary>
        /// 查看文件是否存在
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public bool Exist(string fileName)
        {
            foreach (var value in ResourceDictionary.Values)
            {
                if (value.FileName == fileName)
                    return true;
            }

            return false;
        }

        /// <summary>
        /// 查看文件是否存在
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public ResourceInfo TryGetFileInfo(string filePath)
        {
            ResourceDictionary.TryGetValue(filePath, out var resourceInfo);
            return resourceInfo;
        }

        public void Dispose()
        {
            _binaryReader?.Dispose();
            _fileStream?.Dispose();
        }
    }
}